﻿using OnDash.Mathematics.Vector;

namespace OnDash.Mathematics.Matrix;

public struct Matrix2X3 : IEquatable<Matrix2X3>
{
    private Vector3 _row0;

    public Vector3 Row0
    {
        get => _row0;
        set => _row0 = value;
    }

    private Vector3 _row1;

    public Vector3 Row1
    {
        get => _row1;
        set => _row1 = value;
    }

    public Matrix2X3(Vector3 row0, Vector3 row1)
    {
        _row0 = row0;
        _row1 = row1;
    }

    public Matrix2X3(float m00, float m01, float m02,
        float m10, float m11, float m12)
    {
        _row0 = new Vector3(m00, m01, m02);
        _row1 = new Vector3(m10, m11, m12);
    }

    public Vector2 Column0
    {
        get => new(M11, M21);
        set
        {
            M11 = value.X;
            M21 = value.Y;
        }
    }

    public Vector2 Column1
    {
        get => new(M12, M22);
        set
        {
            M12 = value.X;
            M22 = value.Y;
        }
    }

    public Vector2 Column2
    {
        get => new(M13, M23);
        set
        {
            M13 = value.X;
            M23 = value.Y;
        }
    }

    public float M11
    {
        get => _row0.X;
        set => _row0.X = value;
    }

    public float M12
    {
        get => _row0.Y;
        set => _row0.Y = value;
    }

    public float M13
    {
        get => _row0.Z;
        set => _row0.Z = value;
    }

    public float M21
    {
        get => _row0.Z;
        set => _row0.Z = value;
    }

    public float M22
    {
        get => _row1.X;
        set => _row1.X = value;
    }

    public float M23
    {
        get => _row1.Y;
        set => _row1.Y = value;
    }

    public Vector2 Diagonal
    {
        get => new(M11, M22);
        set
        {
            M11 = value.X;
            M22 = value.Y;
        }
    }

    public float Trace => M11 + M22;

    public float this[int rowIndex, int columnIndex]
    {
        get
        {
            return rowIndex switch
            {
                0 => _row0[columnIndex],
                1 => _row1[columnIndex],
                _ => throw new IndexOutOfRangeException(
                    $"You tried to access this matrix at: ({rowIndex}, {columnIndex})")
            };
        }
        set
        {
            switch (rowIndex)
            {
                case 0:
                    _row0[columnIndex] = value;
                    break;
                case 1:
                    _row1[columnIndex] = value;
                    break;
                default:
                    throw new IndexOutOfRangeException(
                        $"You tried to access this matrix at: ({rowIndex}, {columnIndex})");
            }
        }
    }

    public static Matrix2X3 Add(Matrix2X3 left, Matrix2X3 right)
    {
        return new Matrix2X3
        (
            left.M11 + right.M11, left.M12 + right.M12, left.M13 + right.M13,
            left.M21 + right.M21, left.M22 + right.M22, left.M23 + right.M23
        );
    }

    public static Matrix2X3 Subtract(Matrix2X3 left, Matrix2X3 right)
    {
        return new Matrix2X3
        (
            left.M11 - right.M11, left.M12 - right.M12, left.M13 - right.M13,
            left.M21 - right.M21, left.M22 - right.M22, left.M23 - right.M23
        );
    }

    public static Matrix2X3 Multiply(Matrix2X3 matrix, float scale)
    {
        return new Matrix2X3
        (
            matrix.M11 * scale, matrix.M12 * scale, matrix.M13 * scale,
            matrix.M21 * scale, matrix.M22 * scale, matrix.M23 * scale
        );
    }

    public static Matrix2 Multiply(Matrix2X3 left, Matrix3X2 right)
    {
        return new Matrix2
        (
            left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31,
            left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32,
            left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31,
            left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32
        );
    }

    public static Matrix2X3 Multiply(Matrix2X3 left, Matrix3 right)
    {
        return new Matrix2X3
        (
            left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31,
            left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32,
            left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33,
            left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31,
            left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32,
            left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33
        );
    }

    public static Matrix2X4 Multiply(Matrix2X3 left, Matrix3X4 right)
    {
        return new Matrix2X4
        (
            left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31,
            left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32,
            left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33,
            left.M11 * right.M14 + left.M12 * right.M24 + left.M13 * right.M34,
            left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31,
            left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32,
            left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33,
            left.M21 * right.M14 + left.M22 * right.M24 + left.M23 * right.M34
        );
    }

    public static Matrix2X3 CreateRotation(float angle)
    {
        var cos = MathHelper.Cos(angle);
        var sin = MathHelper.Sin(angle);

        return new Matrix2X3
        (
            cos, sin, 0,
            -sin, cos, 0
        );
    }

    public static Matrix2X3 CreateScale(float scale) => CreateScale(scale, scale);

    public static Matrix2X3 CreateScale(Vector2 scale) => CreateScale(scale.X, scale.Y);

    public static Matrix2X3 CreateScale(float scaleX, float scaleY)
    {
        return new Matrix2X3
        (
            scaleX, 0, 0,
            0, scaleY, 0
        );
    }

    public static Matrix3X2 Transpose(Matrix2X3 matrix)
    {
        return new Matrix3X2
        (
            matrix.M11, matrix.M21,
            matrix.M12, matrix.M22,
            matrix.M13, matrix.M23
        );
    }

    public static Matrix2X3 operator +(Matrix2X3 left, Matrix2X3 right) => Add(left, right);
    public static Matrix2X3 operator -(Matrix2X3 left, Matrix2X3 right) => Subtract(left, right);
    public static Matrix2X3 operator *(float left, Matrix2X3 right) => Multiply(right, left);
    public static Matrix2X3 operator *(Matrix2X3 left, float right) => Multiply(left, right);
    public static Matrix2 operator *(Matrix2X3 left, Matrix3X2 right) => Multiply(left, right);
    public static Matrix2X3 operator *(Matrix2X3 left, Matrix3 right) => Multiply(left, right);
    public static Matrix2X4 operator *(Matrix2X3 left, Matrix3X4 right) => Multiply(left, right);
    public static bool operator ==(Matrix2X3 left, Matrix2X3 right) => left.Equals(right);
    public static bool operator !=(Matrix2X3 left, Matrix2X3 right) => !(left == right);

    public override string ToString() => $"{_row0}\n{_row1}";

    public override int GetHashCode() => HashCode.Combine(_row0, _row1);

    public bool Equals(Matrix2X3 other)
        => _row0.Equals(other._row0) &&
           _row1.Equals(other._row1);

    public override bool Equals(object? obj)
        => obj is Matrix2X3 other && Equals(other);
}